pages/milestones/[milestone].js (258 lines of code) (raw):

import React from 'react'; import useSWR from 'swr'; import { Helmet } from 'react-helmet'; import Link from 'next/link'; import Error from 'next/error'; import { useRouter } from 'next/router'; import { Container, Nav, Navbar, Table } from 'react-bootstrap'; import TimeAgo from 'react-timeago'; import queryString from 'query-string'; import { AlertIcon, HeartIcon, LinkIcon, PersonIcon, } from '@primer/octicons-react'; import { getMilestonePagination, formatIssueData } from 'lib/utils/milestones'; import { dateSort, sortData } from 'lib/utils/sort'; import { getApiURL } from 'lib/utils'; import { validMilestoneRX } from 'lib/const'; import ActiveLink from 'components/ActiveLink'; import HeaderLink from 'components/HeaderLink'; const defaultSort = 'assignee'; const defaultSortDir = 'asc'; const sortConfig = { assignee: {}, priority: {}, title: {}, repo: {}, updatedAt: { sortFunc: dateSort, }, hasProject: {}, state: {}, reviewersNames: {}, }; function getCurrentSortQueryString() { const router = useRouter(); const { sort, dir } = router.query; return `?${queryString.stringify({ dir: dir || defaultSortDir, sort: sort || defaultSort, })}`; } function renderAssignee(issue) { if (issue.assignees.nodes.length) { const issueAssignee = issue.assignees.nodes[0]; return ( <span title={issueAssignee.login}> <img className="avatar" src={issueAssignee.avatarUrl} alt="" />{' '} {issueAssignee.login} </span> ); } if (issue.assignee === '01_contributor') { return ( <span className="contributor"> <HeartIcon verticalAlign="middle" /> Contributor </span> ); } return ( <span className="unassigned"> <PersonIcon verticalAlign="middle" /> Unassigned </span> ); } function renderReviewers(issue) { const reviewers = []; issue.reviewers.forEach((item) => { reviewers.push( <React.Fragment key={`${issue.number}-${item.author.login}`}> <a href={item.prLink} target="_blank" rel="noopener noreferrer"> <img className="avatar" src={item.author.avatarUrl} title={`Reviewed by ${item.author.login}`} alt="" /> </a> </React.Fragment>, ); }); return reviewers; } function renderRows({ data }) { const rows = []; const colSpan = 7; if (!data) { return ( <tr> <td colSpan={colSpan}>Loading...</td> </tr> ); } if (data.length === 0) { return ( <tr> <td colSpan={colSpan}> <p>There are no issues associated with this milestone yet.</p> </td> </tr> ); } for (let i = 0; i < data.length; i++) { const issue = data[i] || {}; rows.push( <tr key={`issue-${i}`}> <td className="assignee">{renderAssignee(issue)}</td> <td> <span className={issue.priority || 'unprioritized'}> {issue.priority ? issue.priority.toUpperCase() : <AlertIcon />} </span> </td> <td> <a className="issueLink" href={issue.url} target="_blank" rel="noopener noreferrer" > <strong>#{issue.number}:</strong> {issue.title}{' '} <LinkIcon verticalAlign="middle" /> </a> {issue.hasProject ? ( <a href={issue.projectUrl} target="_blank" rel="noopener noreferrer" className="projectLink" > {issue.projectName} </a> ) : null} </td> <td>{issue.repository.name.replace('addons-', '')}</td> <td> <TimeAgo date={issue.updatedAt} /> </td> <td> <span className="label" style={{ backgroundColor: issue.stateLabelColor, color: issue.stateLabelTextColor, }} > {issue.stateLabel} </span> </td> <td className="reviewers">{renderReviewers(issue)}</td> </tr>, ); } return rows; } export async function getServerSideProps(props) { const { milestone } = props.params; const milestoneIssuesURL = getApiURL('/api/gh-milestone-issues/', { milestone, }); const res = await fetch(milestoneIssuesURL); const errorCode = res.ok ? false : res.status; const milestoneIssueData = await res.json(); return { props: { errorCode, milestoneIssues: formatIssueData(milestoneIssueData), }, }; } const Milestones = (props) => { if (props.errorCode) { return <Error statusCode={props.errorCode} />; } const router = useRouter(); const { sort, dir, milestone } = router.query; const { groups: { year, month, day }, } = validMilestoneRX.exec(milestone); const milestonePagination = getMilestonePagination({ startDate: new Date(year, month - 1, day), }); const milestoneIssuesURL = getApiURL('/api/gh-milestone-issues/', { milestone, }); const initialMilestoneIssues = props.milestoneIssues; const { data: milestoneIssues } = useSWR( milestoneIssuesURL, async () => { const result = await fetch(milestoneIssuesURL); const json = await result.json(); return formatIssueData(json); }, { fallbackData: initialMilestoneIssues, refreshInterval: 30000 }, ); let data = milestoneIssues; if (sort) { data = sortData({ data, columnKey: sort, direction: dir, sortConfig }); } return ( <div className="Milestones"> <Helmet> <title>Milestones</title> </Helmet> <Navbar variant="muted" bg="light" className="shadow-sm d-flex justify-content-between px-3" sticky="top" > <Nav variant="pills"> <Nav.Item> <Link href={`/milestones/${ milestonePagination.prevFromStart }/${getCurrentSortQueryString()}`} passHref legacyBehavior > <Nav.Link eventKey="prev" className="previous" active={false}> Previous </Nav.Link> </Link> </Nav.Item> <Nav.Item> <Link href={`/milestones/${ milestonePagination.nextFromStart }/${getCurrentSortQueryString()}`} passHref legacyBehavior > <Nav.Link eventKey="next" className="next" active={false}> Next </Nav.Link> </Link> </Nav.Item> </Nav> <Nav variant="pills"> <Nav.Item> <ActiveLink href={`/milestones/${ milestonePagination.current }/${getCurrentSortQueryString()}`} activeClassName="active" passHref > <Nav.Link eventKey="current" className="current"> Current Milestone </Nav.Link> </ActiveLink> </Nav.Item> </Nav> </Navbar> <Container as="main" bg="light"> <h1> Issues for milestone: {milestone.replace(/-/g, '.')} </h1> <Table responsive hover> <thead> <tr> <th className="assignees"> <HeaderLink columnKey="assignee" linkText="Assignee" /> </th> <th> <HeaderLink columnKey="priority" linkText="Priority" /> </th> <th className="issue"> <HeaderLink columnKey="title" linkText="Issue" /> </th> <th className="repo"> <HeaderLink columnKey="repo" linkText="Repo" /> </th> <th className="last-updated"> <HeaderLink columnKey="updatedAt" linkText="Last Update" /> </th> <th className="state"> <HeaderLink columnKey="state" linkText="State" /> </th> <th> <HeaderLink columnKey="reviewersNames" linkText="Reviewers" /> </th> </tr> </thead> <tbody>{renderRows({ data })}</tbody> </Table> </Container> </div> ); }; export default Milestones;